package ggroubig

import (
	"context"
	"encoding/json"
	"github.com/shopspring/decimal"
	"github.com/tidwall/gjson"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"time"
	"wzgames/internal/client"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/roulettep"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/secure/routil"
	"wzgames/internal/services/games"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

type GRouletteBig struct {
	games.GGame

	zn   string
	cfg  *roulettep.RouletteConfig
	gift *routil.RouUtil

	plateMon  *PlateMon
	instIdStr string
	keyBet    string
}

func NewRouletteBig(inst *gamep.GameInst) (games.IGameService, error) {
	roulette := &GRouletteBig{
		instIdStr: strconv.FormatInt(inst.InstID, 10),
		gift:      &routil.RouUtil{},
	}
	roulette.zn = "roulette"
	roulette.SetGameInst(inst)
	err := roulette.updateConf()
	if err != nil {
		logger.Error("err == ", zap.String("error", err.Error()))
		return roulette, err
	}
	roulette.plateMon = newPlate(roulette)
	err = roulette.plateMon.Init(roulette.cfg)
	roulette.keyBet = constant.RouBetNKey + "." + roulette.instIdStr

	return roulette, nil
}

func init() {
	games.RegisterGame(109, NewRouletteBig)
	games.RegisterMarker(109, NewGMarker)
}

func (a *GRouletteBig) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &roulettep.RouletteConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[RouletteBig] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

func (a *GRouletteBig) Update() {
	err := a.updateConf()
	if err == nil {
	}
}

func (a *GRouletteBig) Destroy() {

}

func (a *GRouletteBig) Login(tb *games.GameTable) {
	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	tb.Marker.(*GMarker).SetBetU(&gamep.BetUser{
		AgtId:    tb.Player.AgtID(),
		AgtKey:   tb.Player.AgtKey(),
		Uid:      tb.Player.ID(),
		Uname:    tb.Player.ShowName(),
		Uhead:    tb.Player.UHead(),
		Currency: tb.Seat.Currency,
	})
	a.clientInit(tb)
	a.plateMon.Register(tb)
}

func (a *GRouletteBig) Logout(tb *games.GameTable) {
	a.plateMon.UnRegister(tb)
}

func (a *GRouletteBig) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(10))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))
	roundId, roundState := a.plateMon.RoundInfo()
	xw.WriteLongS(constant.KeyRoundId, roundId)
	xw.WriteIntS(constant.KeyStageId, roundState)
	xw.WriteLongS(constant.KeyRemain, a.plateMon.CurrentElapse())
	xw.Write(a.getFlewsFromCache())

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(3))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyAvatar, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteSfsArrayPre(constant.KeyFreeBets, uint16(0))

	xw.Write(a.cfg.GetConfigBin())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) InstIdStr() string {
	return a.instIdStr
}

func (a *GRouletteBig) getFlewsFromCache() []byte {
	var sb strings.Builder
	sb.WriteString(constant.RouFlewsKey)
	sb.WriteString(a.instIdStr)
	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	ret, err := gloader.CommMgr().RedisPool().Get().Get(ctx, sb.String()).Result()
	cancel()
	if err != nil {
		logger.Error("[RouletteBig] get flews from cache fail on Get", zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return []byte{}
	}
	return byteutil.StringToSliceByte(ret)
}

func (a *GRouletteBig) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerBet:
			a.betHandler(tb, m)
		case constant.HandlerServerSeed:
			a.ServerSeedHandler(tb)
		case constant.HandlerUpdateBalance:
			a.updateBalanceHandler(tb)
		case constant.HandlerRoundFairness:
			a.roundFairnessHandler(tb, m)
		case constant.HandlerGetHistory:
			a.getBetHistory(tb, m)
		case constant.HandlerRoundDetails:
			a.getRoundDetails(tb, m)
		default:
			logger.Warn("[RouletteBig] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	case *games.BetDelay:
		rouBet, ok := m.BetChip.(*statement.RouletteBigBetInfo)
		if !ok {
			return
		}
		ret, ok1 := m.Result.(RoundCounterSettle)
		if !ok1 {
			return
		}
		a.doSettle(m.Tb, m.BetObj, rouBet, ret)
	default:
		logger.Warn("[RouletteBig] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GRouletteBig) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdNextServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeedSHA256, a.plateMon.NextRoundSeedH256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) updateBalanceHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdNewBalance)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.CmdNewBalance, tb.Balance())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) checkBetTotal(betValue string, totalAmt int64, chipEquals, rate float64, precision int32) (bool, string, string) {
	bv, err := decimal.NewFromString(betValue)
	if err != nil {
		return false, "", ""
	}
	d := decimal.NewFromInt(totalAmt).Mul(decimal.NewFromFloat(chipEquals))
	if bv.Equal(d) {
		if rate > 0 {
			p := decimal.NewFromFloat(rate).Div(calcutil.BaseCoefficient)
			fee := d.Mul(p).Truncate(precision)
			effAmt := d.Sub(fee)
			return true, effAmt.String(), fee.String()
		}
		return true, betValue, "0"
	}
	return false, "", ""
}

func (a *GRouletteBig) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	chips := data.GetUtfString(constant.KeyChips)
	if chips == constant.KeyEmptyJson || !gjson.Valid(chips) {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	chipsArr, totalAmt, ok := a.gift.AnalysisChips(chips, a.cfg.GameConfig)
	if !ok {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}
	ckOk, effAmt, fee := a.checkBetTotal(betValue, totalAmt, a.cfg.GameConfig.ChipEquals, a.cfg.GameConfig.Rate, tb.Seat.Precision)
	if !ckOk {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}
	betObj := tb.GenBetObj(mcomm.GameAction_GameBet)
	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseBet, true)
			return
		}
	}
	canBet, currRoundId := a.plateMon.CanBet()
	if !canBet {
		betObj.CkCode = int32(response.ErrForbidden)
		tb.PlayInputData(betObj, exMsg)
		a.RetErr(tb, response.ErrForbidden, constant.ResponseBet)
		return
	}
	betObj.RoundId = currRoundId
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	tb.UpBetObj(betObj)
	betN := &gamep.BetNote{
		BetU:       tb.Marker.(*GMarker).GetBetU(),
		ClientSeed: clientSeed,
		BetStr:     betValue,
		FreeBet:    data.GetBool("freeBet"),
		Dateline:   dateutil.UnixMilli(),
		RoundId:    currRoundId,
		BetNo:      betId,
		Op:         constant.RouBetState,
		Chips:      chips,
	}
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = effAmt
	betObj.Fee = fee
	betObj.FreeBet = betN.FreeBet
	rouBet := &statement.RouletteBigBetInfo{
		ServerSeed: tb.Marker.ServerSeed(),
		ClientSeed: clientSeed,
		Chips:      chips,
		ChipArr:    chipsArr,
	}
	betObj.ImRet = &statement.PlayPayout{
		BetId:   betObj.BetId,
		GameAct: betObj.GameAct,
		GameId:  betObj.GameId,
		RoundId: currRoundId,
	}
	betObj.Detail = a.rouBetInfo(rouBet)
	code := tb.Bet(betObj)
	if code != 0 {
		a.RetErr(tb, code, constant.ResponseBet)
		return
	}
	actorservice.Send(a.plateMon.pid, &games.BetDelay{
		Tb:      tb,
		BetObj:  betObj,
		BetChip: rouBet,
	})

	a.sendBetInfo(a.keyBet, betN)

	betAmt, _ := stringutil.ToFloat64(betValue, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(5))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteBoolS(constant.KeyIsFreeBet, betN.FreeBet)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) doSettle(tb *games.GameTable, betObj *statement.PlayBet, rouBet *statement.RouletteBigBetInfo, ret RoundCounterSettle) {
	betTotal, totalPay := a.gift.CalcMultiplier(a.cfg.GameConfig, rouBet.ChipArr, ret.RetNum, ret.RetColor, ret.RetGrp)
	if totalPay.IsZero() {
		betObj.ImRet.GameAct = mcomm.GameAction_GameLoseSettle
	} else {
		betObj.ImRet.GameAct = mcomm.GameAction_GameCashOut
	}
	multiplier := totalPay.Div(betTotal)
	winAmt := totalPay.Truncate(tb.Seat.Precision).String()
	winAmt, _ = tb.CheckMaxWin(winAmt)
	rouBet.Multiplier = calcutil.RoundFloorD(multiplier)
	rouBet.RetNum = ret.RetNum
	rouBet.RetColor = ret.RetColor
	rouBet.RetGrp = ret.RetGrp
	betObj.ImRet.Win = calcutil.CheckGameResult(multiplier)
	betObj.ImRet.WinAmt = winAmt
	betObj.ImRet.Payout = multiplier.String()
	betObj.ImRet.Dateline = dateutil.UnixMilli()
	betObj.ImRet.Detail = a.rouBetInfo(rouBet)
	code := tb.Payout(betObj)
	if code != 0 {
		a.RetErr(tb, code, constant.ResponseDrawLottery)
		return
	}

	betAmt, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	winAmt1, _ := stringutil.ToFloat64(winAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseDrawLottery)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(9))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betObj.ImRet.Win))
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt1)
	xw.WriteStringS(constant.KeyNum, ret.RetNum)
	xw.WriteStringS(constant.KeyColor, ret.RetColor)
	xw.WriteStringS(constant.KeyGrp, ret.RetGrp)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) rouBetInfo(rouBet *statement.RouletteBigBetInfo) string {
	b, err := json.Marshal(rouBet)
	if err != nil {
		logger.Error("[RouletteBig] do submit fail on json.Marshal roulette bet", zap.Error(err))
		return ""
	}
	return byteutil.SliceByteToString(b)
}

func (a *GRouletteBig) sendBetInfo(key string, bn *gamep.BetNote) {
	if bn == nil {
		return
	}
	b, err := proto.Marshal(bn)
	if err != nil {
		logger.Error("[RouletteBig] send bet to roulette tower fail on proto.Marshal", zap.Int64("AgtId", bn.BetU.AgtId), zap.Uint64("Uid", bn.BetU.Uid), zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return
	}
	_ = gloader.CommMgr().NatsC().Publish(key, b)
}

func (a *GRouletteBig) roundFairnessHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}
	roundIdStr := data.GetUtfString(constant.KeyRoundId)
	roundId, _ := stringutil.ToInt64(roundIdStr, 0)
	if !gvalidator.IsInt(roundIdStr) || roundId <= 0 {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}

	req := &roulettep.RouRoundReq{
		RoundId: roundId,
		InstId:  tb.InstID(),
	}
	reply := &roulettep.RouRoundRes{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
	err := client.GetBetTowerClient().Call(rpcCtx, "RouletteRoundFairness", req, reply)
	cancelFn()
	if err != nil {
		logger.Error("[RouletteBig] query roulette round fairness fail", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", a.GameInstId()), zap.Int64("RoundId", roundId), zap.Error(err))
		a.RetErr(tb, response.ErrInternal, constant.CmdRoundFairness)
		return
	}
	if reply.Code != 0 {
		a.RetErr(tb, int16(reply.Code), constant.CmdRoundFairness)
		return
	}

	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdRoundFairness)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteSfsObjectPre(constant.KeyFairness, uint16(8))
	xw.WriteLongS(constant.KeyRoundId, reply.Data.RoundId)
	xw.WriteLongS(constant.KeyRoundStartDate, reply.Data.Start)
	xw.WriteStringS(constant.KeyServerSeed, reply.Data.Seed)
	xw.WriteStringS(constant.KeySeedSHA256, reply.Data.Seed256)
	xw.WriteStringS(constant.KeyNum, reply.Data.RetNum)
	xw.WriteStringS(constant.KeyColor, reply.Data.RetColor)
	xw.WriteStringS(constant.KeyGrp, reply.Data.RetGrp)

	xw.WriteSfsArrayPre(constant.KeyPlayerSeeds, uint16(len(reply.Data.First3.GetFirst3())))
	for _, betU := range reply.Data.First3.GetFirst3() {
		xw.WriteUByte(sfs.SfsObject)
		xw.WriteUShort(uint16(3))
		xw.WriteStringS(constant.KeySeed, betU.Seed)
		xw.WriteStringS(constant.KeyProfileImage, betU.Uhead)
		xw.WriteStringS(constant.KeyUsername, betU.Uname)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GRouletteBig) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}

func (a *GRouletteBig) getRoundDetails(tb *games.GameTable, m *sfs.TSfsObject) {
	data := m.GetContent()
	if data == nil {
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		return
	}
	betId, _ := stringutil.ToInt64(data.GetUtfString(constant.KeyRoundId), 0)
	if betId <= 0 {
		return
	}

	ret, code := tb.GetUserBetDetail(betId)
	if code != 0 {
		return
	}

	rouletteBet := &statement.RouletteBigBetInfo{}
	err := json.Unmarshal(byteutil.StringToSliceByte(ret.Detail), rouletteBet)
	if err != nil {
		logger.Error("[RouletteBig] bet fail on json.Unmarshal roulette bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return
	}

	betAmt, _ := stringutil.ToFloat64(ret.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(ret.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseRoundDetails)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(12))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteLongS(constant.KeyBetId, betId)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteLongS(constant.KeyTimeRound, ret.BetTime)
	xw.WriteDoubleS(constant.KeyPayout, rouletteBet.Multiplier)
	xw.WriteStringS(constant.KeyClientSeed, rouletteBet.ClientSeed)
	xw.WriteStringS(constant.KeyCurrency, ret.Curr)
	xw.WriteBoolS(constant.KeyIsFreeBetGame, ret.FreeBet)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(ret.Win))
	xw.WriteStringS(constant.KeyServerSeed, rouletteBet.ServerSeed)

	xw.WriteSfsObjectPre(constant.KeyDetails, uint16(4))
	xw.WriteStringS(constant.KeyNum, rouletteBet.RetNum)
	xw.WriteStringS(constant.KeyColor, rouletteBet.RetColor)
	xw.WriteStringS(constant.KeyGrp, rouletteBet.RetGrp)
	lchips := len(rouletteBet.ChipArr)
	xw.WriteSfsObjectPre(constant.KeyChips, uint16(lchips))
	for _, chip := range rouletteBet.ChipArr {
		xw.WriteIntS(chip.Key, int32(chip.BetAmt))
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}
